/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
// JavaClient.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include "jni.h"
#include "JavaClient.h"
#include "java_stream.h"
#include "LibMgrSingleton.h"
#include "DataPTD.h"
#include "SSI_SocketLibAPI.h"
#include "SSI_DataPTDOutput.h"
#include <stdio.h>
#include <direct.h>
#include <vector>

using std::vector;


typedef struct{
  int ID;
  CCommandServer* Srv;
}CmdServerT;

typedef struct{
  int ID;
  CScript* Scr;
}ScriptT;

vector<CmdServerT> Servers;
vector<ScriptT> Scripts;
//vector<ScriptT> Debugs;
static int IDCTR = 100;

static int SOCK_REF_CTR = 0;


CScript* GetScriptByID(int ID, vector<ScriptT>& Set){
  vector<ScriptT>::iterator itor;
  for(itor = Set.begin(); itor != Set.end(); itor++){
    if((*itor).ID == ID){
      return (*itor).Scr;
    }
  }
  return 0;
}

CCommandServer* GetServerByID(int ID, vector<CmdServerT>& Set){
  vector<CmdServerT>::iterator itor;
  for(itor = Set.begin(); itor != Set.end(); itor++){
    if((*itor).ID == ID){
      return (*itor).Srv;
    }
  }
  return 0;
}

void RemoveServerByID(int ID, vector<CmdServerT>& Set){
  vector<CmdServerT>::iterator itor;
  for(itor = Set.begin(); itor != Set.end(); itor++){
    if((*itor).ID == ID){
      (*itor).Srv = 0;
      Set.erase(itor);
      break;
    }
  }
}

int GetID(){
  return IDCTR++;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

//CScript* DebugScript;
//CScript* RunScript;

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

String ExtractString(JNIEnv* env, jstring in){
  
  String Ret;  
  const char *str = env->GetStringUTFChars(in, 0);
  Ret = str;
  env->ReleaseStringUTFChars(in, str);
  return Ret;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
  if(ul_reason_for_call == DLL_PROCESS_DETACH)
	{
    // This causes problems... causes crash when PTD unloads.
    //DeleteLibManager();
    //CSocketSSI::CleanUp();
  }else if(ul_reason_for_call == DLL_PROCESS_DETACH){
    //CSocketSSI::Init();
  }
  return TRUE;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

void initPTDRun(JNIEnv* env, jobject obj, java_stream &astream){
  jclass cls = env->GetObjectClass(obj);
  jmethodID mid = env->GetMethodID(cls, "display", "(Ljava/lang/String;I)V");
  jmethodID did = env->GetMethodID(cls, "getdebug", "(Ljava/lang/String;)Ljava/lang/String;");
  jmethodID getline = env->GetMethodID(cls, "getline", "()Ljava/lang/String;");
  jmethodID dispVarID = env->GetMethodID(cls, "displayVar", "(Ljava/lang/String;)V");
  
  jclass main_cls = env->FindClass("ptd/Main");
  jmethodID getMainFrameID = env->GetStaticMethodID(main_cls, "getMainFrame", "()Lptd/PTDMain;");
  if(main_cls == 0){
    printf("main_cls = 0\n");  // KEEP PRINT
    return;
  }else{
    if(getMainFrameID == 0){
      printf("getMainFramID = 0\n");  // KEEP PRINT
      return;
    }else{
      CDataPTD::my_obj = env->CallStaticObjectMethod(main_cls, getMainFrameID);
    }
  }
  CDataPTD::env = env;

  astream.outType = 0;
  astream.dispID = mid;
  astream.errID = mid;
  astream.warnID = mid;
  astream.failID = mid;
  astream.glID = getline;
  astream.debugID = did;
  astream.env = env;
  astream.obj = obj;
  astream.dispVarID = dispVarID;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

void cleanPTD(){
  if(CDataPTD::GetRegisteredType() > 0){
    Factory_RemoveData(CDataPTD::GetRegisteredType());
  }
  if(CDataPTDOutput::GetRegisteredType() > 0){
    Factory_RemoveData(CDataPTDOutput::GetRegisteredType());
  }
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

void checkSockState(int iType){
  if(iType == 0){
    SOCK_REF_CTR--;
    if(SOCK_REF_CTR <= 0){
      CSocketSSI::CleanUpSingle();
      SOCK_REF_CTR = 0;
    }
  }else{
    if(SOCK_REF_CTR == 0){
      CSocketSSI::Init();
    }
    SOCK_REF_CTR++;
  }
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT void JNICALL 
Java_ptd_PTDScriptThread_displayHelloWorld(JNIEnv *env, jobject obj) 
{
    printf("Chunky Monkey!\n");
    return;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jstring JNICALL 
Java_ptd_PTDScriptThread_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
  char buf[1024];
  const char *str = env->GetStringUTFChars(prompt, 0);
  env->ReleaseStringUTFChars(prompt, str);
  return env->NewStringUTF(buf);
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_runConsole(JNIEnv *env, jobject obj, jstring externals){
  
  java_stream astream;

  initPTDRun(env, obj, astream);
  
  astream.outType = -1;
  astream.Disp("Starting...", 0);  // This should get thrown away (I DONT KNOW WHY I HAVE TO DO THIS)
  astream.outType = 0;

  //CLibraryManager LibMgr;
	//LibMgr.LoadDLLs(aConsole.Engine, "Externals.dat");

  ssi_stream* instream = &astream;
	ssi_stream* outstream = &astream;
  CConsole aConsole(instream, outstream);
 
  const char *extern_dir = env->GetStringUTFChars(externals, 0);
  String sextern = extern_dir;
  sextern = sextern + "\\Externals.dat";
  GetLibManager().LoadDLLs(aConsole.Engine, sextern.Str());
  
  aConsole.Loop();

  GetLibManager().FreeDLLs(aConsole.Engine);  

  // Cant clear factory data... as long as singleton... will break scripts
	//Factory_ClearData();
  cleanPTD();
  return 1;
}


/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_getScriptID(JNIEnv *env, jobject obj){
  return GetID();
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

/*
JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_generateScript(JNIEnv *env, jobject obj, jstring script, jstring externals, jint doPROCESSOR)
{
  // Get the file name
  String FileName;  
  const char *str = env->GetStringUTFChars(script, 0);
  FileName = str;
  env->ReleaseStringUTFChars(script, str);

  printf("ENTERING JAVACLIENT\n");

  java_stream* astream = new java_stream();

  ssi_stream* instream = astream;
	ssi_stream* outstream = astream;
  initPTDRun(env, obj, *astream);

  astream->outType = -1;
  astream->Disp("Starting...", 0);  // This should get thrown away (I DONT KNOW WHY I HAVE TO DO THIS)
  astream->outType = 0;

  
  //CScript aScript(instream, outstream);
  CScript* aScript = new CScript(instream, outstream);
  aScript->Load(FileName);

  if(doPROCESSOR){
    CDataPTD aData;
    aScript->Engine->AddVariable(aData, String("PTD"));
  }

  const char *extern_dir = env->GetStringUTFChars(externals, 0);
  String sextern = extern_dir;
  sextern = sextern + "\\Externals.dat";
  GetLibManager().LoadDLLs(aScript->Engine, sextern.Str());

  // Load external libraries
  astream->SetEngine(aScript->Engine);

  ScriptT T;
  T.Scr = aScript;
  T.ID = GetID();
  Scripts.push_back(T);

  Java_ptd_PTDScriptThread_runScript(env, obj, 0, T.ID);

  return T.ID;
}
*/

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_runScript(JNIEnv *env, jobject obj, jstring script, jstring args, jstring externals, jobject Output, jint doDEBUG, jint ID)
{
  // Get the file name

// Cannot use the object passed here, since it is only a local variable in Java
//env->MonitorEnter(obj);

  String FileName;
  String Args;

  const char *str = env->GetStringUTFChars(script, 0);
  FileName = str;
  env->ReleaseStringUTFChars(script, str);

  if(args){
    Args = ExtractString(env, args);
  }

  java_stream* astream = new java_stream();

  ssi_stream* instream = astream;
	ssi_stream* outstream = astream;
  initPTDRun(env, obj, *astream);

  astream->outType = -1;
  astream->Disp("Starting...", 0);  // This should get thrown away (I DONT KNOW WHY I HAVE TO DO THIS)
  astream->outType = 0;

  
  //CScript aScript(instream, outstream);
  CScript* aScript = new CScript(instream, outstream);
  aScript->Load(FileName);
  if(!aScript->EvaluateArguments(Args)){
    return 0;
  }


  int doPROCESSOR = 0;

  if(Output != 0){
    doPROCESSOR = 1;
  }

  if(doPROCESSOR){
    aScript->Engine->SetPackageName("PTD");
    CDataPTDOutput aData;
    aData.my_obj = Output;
    aScript->Engine->AddVariable(aData, String("PTDOutput"));
    //CDataPTD aData;
    //aScript->Engine->AddVariable(aData, String("PTD"));
  }

  const char *extern_dir = env->GetStringUTFChars(externals, 0);
  String sextern = extern_dir;
  sextern = sextern + "\\Externals.dat";
  GetLibManager().LoadDLLs(aScript->Engine, sextern.Str());

  // Load external libraries
  astream->SetEngine(aScript->Engine);

  ScriptT T;
  T.Scr = aScript;
  T.ID = ID;
  Scripts.push_back(T);

  //CLibraryManager LibMgr;
	//LibMgr.LoadDLLs(aScript.Engine, "Externals.dat");
  // BE CAREFUL WITH A LOCAL LIBMGR
  // OK, cant use local mgr, because when it is deleted it will free
  // all the DLLs, even if the console is still using it.

  //char bb[1024];
  //getcwd(bb, 1024);

  if(!aScript){
    return 0;
  }


	if(doDEBUG){
    aScript->Run(DEBUG_MODE);
	}else{
		aScript->Run(NORMAL_MODE);
	}

  vector<ScriptT>::iterator itor;
  for(itor = Scripts.begin(); itor != Scripts.end(); itor++){    
    if((*itor).ID == ID){
      Scripts.erase(itor);
      delete (*itor).Scr->input;
      delete (*itor).Scr;
      (*itor).Scr = 0;
      break;
    }
  }

  //DebugScript = 0;
  //RunScript = 0;


  //outstream->Disp("FINISHED", 1);
  // Also, cant rightly clear the Factory data, because
  // will destroy the factory data for the console...
	//Factory_ClearData();
  
  //}

  GetLibManager().FreeDLLs(aScript->Engine);
  cleanPTD();

// Cannot use the object passed here
//env->MonitorExit(obj);

  return 1;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_getRemoteID(JNIEnv *env, jobject obj){
  return GetID();
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_runRemoteScript(JNIEnv *env, jobject obj, jstring script, jstring args, jstring host, jint port, jint doDEBUG, jint ID)
{
  java_stream out;
  initPTDRun(env, obj, out);

  //out.bCheckSocketError = 1;
  if(host == 0){
    out.ErrorDisp("[SERVER] Error*** No host specified", 1);
    return 0;
  }

  if(script == 0){
    out.ErrorDisp("[SERVER] Error*** No script specified (internal)", 1);
    return 0;
  }



  String pcHost = ExtractString(env, host);
  String Script = ExtractString(env, script);
  String Args = ExtractString(env, args);

  if(Script.GetLength() <= 0){
    out.ErrorDisp("[SERVER] Error*** No script specified", 1);
    return 0;
  }


  checkSockState(1);

  CCommandServer Srv;
  if(!Srv.Init(pcHost, port, out)){
    checkSockState(0);
    CSocketSSI::CleanUp();
    return 0;
  }

  int flags = 0;
  CmdServerT S;
  S.Srv = &Srv;
  S.ID = ID;
  Servers.push_back(S);

  if(Args.GetLength() > 0){
    Srv.setArguments(Args);
  }

  if(!Srv.runScript(Script, flags, out)){
    out.ErrorDisp("[SERVER] Error*** runScript failed", 1);
    CSocketSSI::CleanUp();
    checkSockState(0);
    RemoveServerByID(ID, Servers);
    return 0;
  }
  //}else{
  //  if(!Srv.runConsole(out)){
  //    out.Format("[SERVER] Error*** runConsole failed");
  //    CSocketSSI::CleanUp();
  //    return 0;
  //  }
  //}

  if(!Srv.Loop(out)){
    String s;
    s.Format("[SERVER] Error*** Script loop failed (%d)", CSocketSSI::GetLastError()); 
    out.ErrorDisp(s.Str(), 1);
    CSocketSSI::CleanUp();
    checkSockState(0);
    RemoveServerByID(ID, Servers);
    return 0;
  }
  CSocketSSI::CleanUp();
  RemoveServerByID(ID, Servers);
  checkSockState(0);

  
  return 1;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

// Evidently not needed?
JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_processDebug(JNIEnv *env, jobject obj, jstring script)
{
  return 1;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_getDebugLine(JNIEnv *env, jobject obj, jint ID)
{
  CScript* DebugScript = GetScriptByID(ID, Scripts);
  if(DebugScript){
    CScriptLine aLine;
    ScriptNode* aNode = DebugScript->GetCurrLine();
    if(aNode){
      aNode->Get(aLine);
      return aLine.LineNum;
    }else{
      return -1;
    }
  }else{
    return -1;
  }  
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_killScript(JNIEnv *env, jobject obj, jint ID)
{
  CScript* RunScript = GetScriptByID(ID, Scripts);
  if(RunScript){
    RunScript->Interp->SetStopFlag();
  }else{
    return 0;
  }
  
  return 1;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_killRemoteScript(JNIEnv *env, jobject obj, jint ID)
{
  CCommandServer* Srv = GetServerByID(ID, Servers);
  if(Srv){
    //RunScript->Interp->SetStopFlag();
    Srv->killConnection();
  }else{
    return 0;
  }
  
  return 1;
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jstring JNICALL Java_ptd_PTDScriptThread_getDebugFile(JNIEnv *env, jobject obj, jint ID)
{
  char buf[1024];
  CScript* DebugScript = GetScriptByID(ID, Scripts);
  if(DebugScript){
    sprintf(buf, "%s", DebugScript->GetFileName().Str());
  }else{
    sprintf(buf, "ERROR - NO DEBUGGER");
  }
  
  return env->NewStringUTF(buf);
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jstring JNICALL Java_ptd_PTDScriptThread_getVariableValue(JNIEnv *env, jobject obj, jstring var, jint ID)
{
  char buf[1024];
  CScript* DebugScript = GetScriptByID(ID, Scripts);
  if(!DebugScript){
    sprintf(buf, "ERROR - NO DEBUGGER");
  }else{
    CVariable Var;

    const char *str = env->GetStringUTFChars(var, 0);
    Var.Name = str;
    env->ReleaseStringUTFChars(var, str);
    if(DebugScript->Engine->GetVariable(Var)){
      sprintf(buf, "%s", Var.GetWorkingData().GetString().Str());
    }else{
      sprintf(buf, "Variable not found");
    }
  }
  
  return env->NewStringUTF(buf);
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////

JNIEXPORT jint JNICALL Java_ptd_PTDScriptThread_initPTD(JNIEnv * env, jobject Caller, jobject MainFrame){

//  jclass cls;
//  if(MainFrame != 0){
//    cls = env->GetObjectClass(MainFrame);
//  }else{
//    return 0;
//  }

//  CDataPTD::getOutputID = env->GetMethodID(cls, "getCurrentOutput", "()Lptd/PTDSuiteDisplay;");
//  CDataPTD::testMessage = env->GetMethodID(cls, "testMessage", "()V");

//  if(CDataPTD::getOutputID == 0){
//    return 0;
//  }

  //jobject outp = env->CallObjectMethod(MainFrame, CDataPTD::getOutputID);
  //jclass outcls = 0;
  jclass outcls = env->FindClass("ptd/PTDSuiteDisplay");
  if(outcls == 0){
    printf("outcls is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getOutputID = env->GetMethodID(outcls, "getOutput", "()Ljava/lang/String;");
  if(CDataPTDOutput::getOutputID == 0){
    printf("PTDOutput getOutputID is 0\n"); // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getChildCountID = env->GetMethodID(outcls, "getChildCount", "()I");
  if(CDataPTDOutput::getChildCountID == 0){
    printf("PTDOutput getChildCountID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getChildID = env->GetMethodID(outcls, "getChildAt", "(I)Lptd/PTDSuiteDisplay;");
  if(CDataPTDOutput::getChildID == 0){
    printf("PTDOutput getChildID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getErrorCountID = env->GetMethodID(outcls, "getErrCnt", "()I");
  if(CDataPTDOutput::getErrorCountID == 0){
    printf("PTDOutput getErrorCountID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getWarningCountID = env->GetMethodID(outcls, "getWarnCnt", "()I");
  if(CDataPTDOutput::getWarningCountID == 0){
    printf("PTDOutput getWarningCountID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getNameID = env->GetMethodID(outcls, "getName", "()Ljava/lang/String;");
  if(CDataPTDOutput::getNameID == 0){
    printf("PTDOutput getNameID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getParentID = env->GetMethodID(outcls, "getParentDisplay", "()Lptd/PTDSuiteDisplay;");
  if(CDataPTDOutput::getParentID == 0){
    printf("PTDOutput getParentID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::hasParentID = env->GetMethodID(outcls, "hasParentDisplay", "()Z");
  if(CDataPTDOutput::hasParentID == 0){
    printf("PTDOutput hasParentID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::saveAsTextID = env->GetMethodID(outcls, "saveAsText", "(Ljava/lang/String;)Z");
  if(CDataPTDOutput::saveAsTextID == 0){
    printf("PTDOutput saveAsTextID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::saveAsHTMLID = env->GetMethodID(outcls, "saveAsHTML", "(Ljava/lang/String;)Z");
  if(CDataPTDOutput::saveAsHTMLID == 0){
    printf("PTDOutput saveAsHTMLID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::getRemoteMachineID = env->GetMethodID(outcls, "getRemoteMachine", "()Ljava/lang/String;");
  if(CDataPTDOutput::getRemoteMachineID == 0){
    printf("PTDOutput getRemoteMachineID is 0\n");  // KEEP PRINT
    return 0;
  }

  CDataPTDOutput::haltExecutionID = env->GetMethodID(outcls, "haltExecution", "()V");
  if(CDataPTDOutput::haltExecutionID == 0){
    printf("PTDOutput haltExecutionID is 0\n");  // KEEP PRINT
    return 0;
  }


//  CDataPTD::my_class = cls;
  CDataPTD::env = env;  // Still required

  CDataPTDOutput aPTDOutput;  // I think this has to happen first...
//  CDataPTD aPTD; // This has to happen last because initData?

  return 1;
}

JNIEXPORT jstring JNICALL Java_ptd_PTDScriptThread_getPhantomInfo(JNIEnv *env, jobject obj, jstring externals){
  java_stream* astream = new java_stream();

  ssi_stream* instream = astream;
	ssi_stream* outstream = astream;

  CScript* aScript = new CScript(instream, outstream);

  const char *extern_dir = env->GetStringUTFChars(externals, 0);
  String sextern = extern_dir;
  sextern = sextern + "\\Externals.dat";
  GetLibManager().LoadDLLs(aScript->Engine, sextern.Str());

  // Load external libraries
  astream->SetEngine(aScript->Engine);

  String s = "";
  CEngine* E = aScript->Engine;
  s = E->GetVariableTypes(String(";"));
  s = s + "%";
  s = s + E->GetPackageContents(String("%"), String("#"));
  s = s + "&";
  s = s + E->GetAllVariableMembers(String("%"), String("#"));

  GetLibManager().FreeDLLs(aScript->Engine);
  delete aScript;
  delete astream;

  return env->NewStringUTF(s.Str());
}